package org.incubator;

import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import java.util.Set;

import org.apache.commons.collections.set.ListOrderedSet;

/**
 * Retrieves a random element from a collection of possibilities.
 * 
 * @author Jeroen van Schagen
 * @sine 29-04-2011
 * 
 * @param <T> type of elements being selected
 */
public class RandomElementGenerator<T> implements Generator<T> {
    private final Random random = new Random();
    private final ListOrderedSet elements;

    public RandomElementGenerator() {
        elements = new ListOrderedSet();
    }

    public RandomElementGenerator(Set<T> inclusions) {
        elements = ListOrderedSet.decorate(inclusions);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public T generate() {
        if (elements.isEmpty()) {
            return null;
        }
        int index = random.nextInt(elements.size());
        return (T) elements.get(index);
    }

    // Fluent configuration methods

    /**
     * Include a specific element to our possible values.
     * @param element the element to be included
     * @return this generator, with the included element
     */
    public RandomElementGenerator<T> include(T element) {
        elements.add(element);
        return this;
    }

    /**
     * Include an array of elements to our possible values.
     * @param inclusions the elements to be included
     * @return this generator, with the included elements
     */
    public RandomElementGenerator<T> includeElements(T... inclusions) {
        return this.includeElements(Arrays.asList(inclusions));
    }

    /**
     * Include a collection of elements to our possible values.
     * @param inclusions the elements to be included
     * @return this generator, with the included elements
     */
    public RandomElementGenerator<T> includeElements(Collection<T> inclusions) {
        this.elements.addAll(inclusions);
        return this;
    }

    /**
     * Exclude a specific element from our possible values.
     * @param element the element to be excluded
     * @return this generator, without the excluded element
     */
    public RandomElementGenerator<T> exclude(T element) {
        elements.remove(element);
        return this;
    }

    /**
     * Exclude an array of elements from our possible values.
     * @param exclusions the elements to be excluded
     * @return this generator, without the excluded elements
     */
    public RandomElementGenerator<T> excludeElements(T... exclusions) {
        return this.includeElements(Arrays.asList(exclusions));
    }

    /**
     * Exclude a collection of elements from our possible values.
     * @param exclusions the elements to be excluded
     * @return this generator, without the excluded elements
     */
    public RandomElementGenerator<T> excludeElements(Collection<T> exclusions) {
        this.elements.removeAll(exclusions);
        return this;
    }

}
